import threading
import socketserver
import json
import os
import time
import ssl


class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
    def send_reply(self, reply):
        sendData = bytes(json.dumps(reply), "utf8")
        sendDataLength = len(sendData).to_bytes(4, 'big')
        self.request.sendall(sendDataLength)
        self.request.sendall(sendData)

    def handle(self):
        cert = self.request.getpeercert(binary_form=False)
        localDir = '/home/lighthouse/BlankApp1/user'
        isAdmin = False
        for value in cert["subject"]:
            if value[0][0] == 'commonName':
                if value[0][1] == 'Wu Guozhang':
                    localDir = '/home/lighthouse/BlankApp1/admin'
                    isAdmin = True
                break

        while True:
            data = self.request.recv(4)
            if len(data) == 0:
                break
            data = int.from_bytes(data, 'big', signed=False)
            data = self.request.recv(data)
            if len(data) == 0:
                break
            data = data.strip()
            data = json.loads(data)
            if data["type"] == "ping":
                reply = {"type": "pong"}
                self.send_reply(reply)
            elif data["type"] == "folder":
                reply = []
                requestPath = data["path"]
                realPath = localDir + requestPath
                if requestPath.find("/") == 0 and (requestPath.find("/..") != len(requestPath) - 3 or requestPath.find("/..") == -1) and requestPath.find("/../") == -1 and os.path.isdir(realPath):
                    items = os.scandir(realPath)
                    for item in items:
                        if item.is_dir():
                            reply.append({"name": item.name, "type": "directory"})
                        else:
                            reply.append({"name": item.name, "type": "file"})
                self.send_reply(reply)
            elif data["type"] == "file":
                requestPath = data["path"]
                realPath = localDir + requestPath
                if requestPath.find("/") == 0 and (requestPath.find("/..") != len(requestPath) - 3 or requestPath.find("/..") == -1) and requestPath.find("/../") == -1 and os.path.isfile(realPath):
                    size = os.path.getsize(realPath)
                    sendDataLength = size.to_bytes(4, 'big')
                    self.request.sendall(sendDataLength)
                    file = open(realPath, 'rb')
                    i = 0
                    while size > 0:
                        thisSize = 0
                        if size > 4096:
                            thisSize = 4096
                        else:
                            thisSize = size
                        buf = file.read(thisSize)
                        size -= len(buf)
                        self.request.sendall(buf)
                        i = i + 1
                        if i == 32:
                            i = 0
                            if not isAdmin:
                                time.sleep(1)
                            else:
                                time.sleep(0.1)
                    file.close()
            elif data["type"] == "upload":
                requestPath = data["path"]
                realPath = localDir + requestPath
                fileName = data["file"]
                size = int(data["size"])
                if requestPath.find("/") == 0 and (requestPath.find("/..") != len(requestPath) - 3 or requestPath.find("/..") == -1) and requestPath.find("/../") == -1 and os.path.isdir(realPath) and fileName != "." and fileName != ".." and fileName != "" and fileName.find("/") == -1 and not os.path.isfile(os.path.join(realPath, fileName)) and not os.path.isdir(os.path.join(realPath, fileName)):
                    path = os.path.join(realPath, fileName)
                    sendDataLength = (0).to_bytes(4, 'big')
                    self.request.sendall(sendDataLength)
                    file = open(path, 'wb')
                    while size > 0:
                        thisSize = 0
                        if size > 4096:
                            thisSize = 4096
                        else:
                            thisSize = size
                        buf = self.request.recv(thisSize)
                        size -= len(buf)
                        file.write(buf)
                    sendDataLength = (0).to_bytes(4, 'big')
                    self.request.sendall(sendDataLength)
                    file.close()
                else:
                    sendDataLength = (1).to_bytes(4, 'big')
                    self.request.sendall(sendDataLength)


class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    def server_bind(self) -> None:
        socketserver.TCPServer.server_bind(self)
        self.socket = ssl.wrap_socket(self.socket, keyfile='server-key.pem', certfile='server-cert.cer', server_side=True, cert_reqs=ssl.CERT_REQUIRED, ca_certs='ca-cert.cer', ssl_version=ssl.PROTOCOL_TLSv1_2, do_handshake_on_connect=False)

    def get_request(self):
        (socket, addr) = socketserver.TCPServer.get_request(self)
        socket.do_handshake()
        return (socket, addr)


if __name__ == "__main__":
    HOST, PORT = "0.0.0.0", 1337

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    with server:
        ip, port = server.server_address

        server_thread = threading.Thread(target=server.serve_forever)
        server_thread.daemon = True
        server_thread.start()
        while (True):
            time.sleep(1000)